//////////////////////////////////////////////////////////////////////////
/// Copyright (c) 2011, Sidorenko 'PC' Alexander                       ///
/// mailto: sidorenko /*dot*/ alexander (at) gmail [dot] com           ///
/// This file is a part of the Radiating Gas Dynamics project          ///
//////////////////////////////////////////////////////////////////////////

#ifndef GodunovSolverMT_INCLUDED
#define GodunovSolverMT_INCLUDED

#include "RiemannSolver.h"
#include "Problem.h"

#ifdef _PROFILE
#include "Timer.h"
#endif

//#ifdef _DEBUG
//	#define _DEBUG_PRINT(format, ...) _cprintf(format, __VA_ARGS__);
//#else
	#define _DEBUG_PRINT(format, ...) 
//#endif

class ProblemListener;

//const gdScalar R = gdScalar(8.31447215151515); // Universal Gas Constant
const gdScalar R = gdScalar(287); // Universal Gas Constant

extern unsigned int appliedTimes;

#ifdef _PROFILE
extern TimingsList g_zeroRatesTimings;
extern TimingsList g_normalizeRatesTimings;
extern TimingsList g_updateRatesTimings;
extern TimingsList g_evaluateStepTimings;
extern TimingsList g_applyRatesTimings;
extern TimingsList g_updateStatesTimings;
extern TimingsList g_findMinMaxTimings;

void printTimings(FILE *file, const TimingsList &lst, const char* name);

void dumpTimings();
#endif

struct Events
{
	HANDLE jobDone;
	HANDLE startJob;
};

template<unsigned int N> // Problem's dimensions
class GodunovSolverMT
{
template<unsigned int N>
friend class GodunovWorker;
public:

	typedef typename VecN<N> Vec;

	// Cell structure represents a single cell
	struct Cell
	{

		Cell() {}
		~Cell() {}

		gdScalar dv;// ** Cell's volume
		gdScalar r; // * Density
		gdScalar p; // Pressure
		gdScalar T; // Absolute temperature
		Vec		 v; // Velocity
		gdScalar a; // Sound speed
		gdScalar e; // * Specific full energy
		Vec		 u; // * Volume specific momentum
		// * are Conservative variables. They are used to calculate
		// the rest of the variables
		// ** grid's property. Set up during initialization and stays constant
		// during calculation
		// Pressure, Velocity and Sound Speed are auxiliary
		// variables used by solver. You can use these values
		// for plotting/another kind of output, but please,
		// apply boundary condition TO CONSERVED VARIABLES ONLY

		gdScalar r_rate;
		gdScalar e_rate;
		Vec		 u_rate;
		// Rates are actually reflect change of the specific
		// conservative variable during timestep
	};

	typedef std::vector<Cell> Grid;

	struct Face
	{
		Face() {}
		~Face() {}

		unsigned int fc[2]; // * Cell ids
		gdScalar da;		// * Face area
		Vec		 n;			// * Face unit normal
		gdScalar ir;		// Interface interface
		gdScalar ip;		// Interface pressure
		gdScalar ia;		// Interface sound speed
		gdScalar ivn;		// Interface normal component of velocity
	};
	// * Are grid properties. It's set up during initialization and stays
	// constant during calculation

	typedef std::vector<Face> Faces;

	// cfl coefficient is used for CFL condition
	// cfl coefficient limits the time step to the value small
	// enough, not to allow Riemann waves to propagate more
	// than half size of a control cell
	GodunovSolverMT(gdScalar gamma, gdScalar cfl, unsigned int numThreads, BOOL saveFull);
	virtual ~GodunovSolverMT();

	void initModel(unsigned int numCells,
				   unsigned int numFaces,
				   gdScalar minCellSize,
				   // Riemann Solver parameters
				   // tolerance = 1e-6, stepsThreshold = 10 can be used
				   gdScalar tolerance, unsigned int stepsThreshold);
	inline const Grid& getCurrentState() const { return m_grid; }

protected:

	struct SolveResult
	{
		Cell minValues;
		Cell maxValues;
		unsigned int numTimesteps;
		gdScalar modelTime;
		unsigned int solvingTime;
	};

	// Non thread-safe get cell
	Cell& getCell(unsigned int id);
	// Non thread-safe get face
	Face& getFace(unsigned int id);

	// Thread-safe get cell
	Cell& getCellLock(unsigned int id);
	void freeCellLock(unsigned int id);
	// Thread-safe get face
	Face& getFaceLock(unsigned int id);
	void freeFaceLock(unsigned int id);

	SolveResult solve(gdScalar time, FILE* file);

	inline void setListener(ProblemListener* pListener) { m_pListener = pListener; }

	// Methods used by Workers
	inline gdScalar getDT() const { return m_dt; }
	inline bool checkIfWork() const { return m_work; }

	// Grid
	void resizeGrid(unsigned int numCells,
					unsigned int numFaces);
	virtual void generateGrid() = 0;
	virtual void generateInitialDistributions() = 0;
	virtual void applyBoundaryConditions() = 0;

	gdScalar m_gamma, m_gamma1;
	gdScalar m_cfl;
	gdScalar m_minCellSize;
	Faces m_faces;
	Grid  m_grid;
	unsigned int m_numThreads;
	unsigned int m_solvingTime;

	gdScalar m_dt;
	bool m_work;

	BOOL m_saveFull;

	ProblemListener *m_pListener;

	struct FileHeader
	{
		Problem::Type type;
		unsigned int dimensions[N];
		unsigned int numTimesteps;
		unsigned int solvingTime;
		unsigned int numThreads;
		gdScalar minCellSize;
		gdScalar gamma;
		gdScalar cfl;
		gdScalar modelTime;
		gdScalar precision;
		Cell minVals, maxVals;
	};

	void writeOneStep(FILE* file, const Grid& grid, gdScalar time);

	RiemannSolver<N> m_riemann;

private:

	void SendEvt();
	void WaitForEvt();

	std::vector<Events> m_events;
	std::vector<HANDLE> m_jobDoneEvents;

};

template<unsigned int N>
class GodunovWorker<N>
{
template<unsigned int N>
friend class GodunovSolverMT;
public:

	GodunovWorker(GodunovSolverMT<N> &solver,
			unsigned int firstCell, unsigned int lastCell,
			unsigned int firstFace, unsigned int lastFace,
			Events events)
			: m_solver(solver) 
			, m_firstCell(firstCell)
			, m_lastCell(lastCell)
			, m_firstFace(firstFace)
			, m_lastFace(lastFace)
			, m_events(events)
	{
	}

	void work();

	inline gdScalar getCFL() const { return m_stepCFL; }

protected:

	static DWORD WINAPI ThreadProc(LPVOID param);

	void zeroRates();
	void normalizeRates();
	void updateRates();
	void evaluateStepCFL();
	void applyRates();
	void updateStates();
	void findMinMax();

	void fireEvt() const {
		SetEvent(m_events.jobDone);
		_DEBUG_PRINT("GodunovWorker::fireEvent JOB DONE EVENT SET\n");
	}

	void waitForEvt() const { 
		WaitForSingleObject(m_events.startJob, INFINITE); 
		_DEBUG_PRINT("GodunovWorker::waitForEvt START JOB EVENT RECEIVED\n");
	}

	GodunovSolverMT<N> &m_solver;
	unsigned int m_firstCell, m_lastCell;
	unsigned int m_firstFace, m_lastFace;
	gdScalar m_stepCFL;
	typename GodunovSolverMT<N>::Cell m_minValues, m_maxValues;

	Events m_events;

};

template<unsigned int N>
DWORD WINAPI GodunovWorker<N>::ThreadProc(LPVOID param)
{
	GodunovWorker<N> *ths = reinterpret_cast<GodunovWorker<N>*>(param);

	_DEBUG_PRINT("GodunovWorker::ThreadProc started\n");

	while(1)
	{
		ths->zeroRates();

		ths->updateRates();

		ths->normalizeRates();

		ths->evaluateStepCFL();

		ths->applyRates();

		ths->updateStates();

		ths->findMinMax();

		ths->waitForEvt();
		if (!ths->m_solver.checkIfWork())
			break;
		ths->fireEvt();
	}
	ths->fireEvt();

#ifdef _PROFILE
	dumpTimings();
#endif

	_DEBUG_PRINT("GodunovWorker::ThreadProc ended\n");

	return 0;
}

template<unsigned int N>
void GodunovWorker<N>::zeroRates()
{
	_DEBUG_PRINT("GodunovWorker::zeroRates started\n");
	waitForEvt();
#ifdef _PROFILE
	Timer t;
	t.reset();
#endif
	// Zero rates
	for (unsigned int i = m_firstCell; i < m_lastCell; ++i)
	{
		GodunovSolverMT<N>::Cell& c = m_solver.getCellLock(i);
		c.e_rate = gdScalar(0.0);
		c.r_rate = gdScalar(0.0);
		for (unsigned int j = 0; j < N; ++j)
			c.u_rate[j] = gdScalar(0);
		m_solver.freeCellLock(i);
	}
#ifdef _PROFILE
	g_zeroRatesTimings.addTiming(t.getTimeMiliseconds());
#endif
	fireEvt();
	_DEBUG_PRINT("GodunovWorker::zeroRates ended\n");
}

template<unsigned int N>
void GodunovWorker<N>::updateRates()
{
	_DEBUG_PRINT("GodunovWorker::updateRates started\n");
	waitForEvt();
#ifdef _PROFILE
	Timer t;
	t.reset();
#endif
	// Calculate fluxes and rates
	for (unsigned int i = m_firstFace; i < m_lastFace; ++i)
	{
		GodunovSolverMT<N>::Face& f = m_solver.getFaceLock(i);
		unsigned int i1 = f.fc[0],
					 i2 = f.fc[1];
		GodunovSolverMT<N>::Cell& c1 = m_solver.getCellLock(i1);
		GodunovSolverMT<N>::Cell& c2 = m_solver.getCellLock(i2);
		// Solve Riemann Problem
		gdScalar vn, r, a, p;
		GodunovSolverMT<N>::Vec vt;
		m_solver.m_riemann.interfaceState(f.n, c1.r, c1.a, c1.p, c1.v,
									  c2.r, c2.a, c2.p, c2.v,
								 r, a, p, vn, vt);

		// Calculate Fluxes
		f.ir = r;
		f.ia = a;
		f.ip = p;
		f.ivn = vn;
		GodunovSolverMT<N>::Vec v = vt + f.n * vn;
		gdScalar r_flux = r * vn * f.da;
		gdScalar e_flux = r_flux * (a * a / m_solver.m_gamma1 + gdScalar(0.5) * v.dotProduct(v));
		GodunovSolverMT<N>::Vec u_flux = v * r_flux + f.n * f.da * p;
		
		// Apply Fluxes to Rates
		//////////////////////////////////////////////////////////////////////////
		c1.r_rate -= r_flux;
		c2.r_rate += r_flux;

		c1.e_rate -= e_flux;
		c2.e_rate += e_flux;

		c1.u_rate -= u_flux;
		c2.u_rate += u_flux;
		m_solver.freeFaceLock(i);
		m_solver.freeCellLock(i1);
		m_solver.freeCellLock(i2);
	}
#ifdef _PROFILE
	g_updateRatesTimings.addTiming(t.getTimeMiliseconds());
#endif
	fireEvt();
	_DEBUG_PRINT("GodunovWorker::updateRates ended\n");
}

template<unsigned int N>
void GodunovWorker<N>::normalizeRates()
{
	_DEBUG_PRINT("GodunovWorker::normalizeRates started\n");
	waitForEvt();
#ifdef _PROFILE
	Timer t;
	t.reset();
#endif
	// Normalize rates
	for (unsigned int i = m_firstCell; i < m_lastCell; ++i)
	{
		GodunovSolverMT<N>::Cell& c = m_solver.getCellLock(i);
		c.r_rate /= c.dv;
		c.e_rate /= c.dv;
		c.u_rate /= c.dv;
		m_solver.freeCellLock(i);
	}
#ifdef _PROFILE
	g_normalizeRatesTimings.addTiming(t.getTimeMiliseconds());
#endif
	fireEvt();
	_DEBUG_PRINT("GodunovWorker::normalizeRates ended\n");
}

template<unsigned int N>
void GodunovWorker<N>::evaluateStepCFL()
{
	_DEBUG_PRINT("GodunovWorker::evaluateStepCFL started\n");
	waitForEvt();
#ifdef _PROFILE
	Timer t;
	t.reset();
#endif
	gdScalar maxDivider = -FLT_MAX, divider;
	// We access faces read-only, so no need to lock
	for (unsigned int i = m_firstFace; i < m_lastFace; ++i)
	{
		const GodunovSolverMT<N>::Face& f = m_solver.getFace(i);
		divider = f.ia + abs(f.ivn);
		maxDivider = max(divider, maxDivider);
	}
	m_stepCFL = m_solver.m_cfl * m_solver.m_minCellSize / maxDivider;
#ifdef _PROFILE
	g_evaluateStepTimings.addTiming(t.getTimeMiliseconds());
#endif
	fireEvt();
	_DEBUG_PRINT("GodunovWorker::evaluateStepCFL ended\n");
}

template<unsigned int N>
void GodunovWorker<N>::applyRates()
{
	appliedTimes++;
	_DEBUG_PRINT("GodunovWorker::applyRates started\n");
	waitForEvt();
#ifdef _PROFILE
	Timer t;
	t.reset();
#endif
	gdScalar dt = m_solver.getDT();
	for (unsigned int i = m_firstCell; i < m_lastCell; ++i)
	{
		GodunovSolverMT<N>::Cell &c = m_solver.getCellLock(i);
		c.r += c.r_rate * dt;
		c.e += c.e_rate * dt;
		c.u += c.u_rate * dt;
		m_solver.freeCellLock(i);
	}
#ifdef _PROFILE
	g_applyRatesTimings.addTiming(t.getTimeMiliseconds());
#endif
	fireEvt();
	_DEBUG_PRINT("GodunovWorker::applyRates ended\n");
}

template<unsigned int N>
void GodunovWorker<N>::updateStates()
{
	_DEBUG_PRINT("GodunovWorker::updateStates started\n");
	waitForEvt();
#ifdef _PROFILE
	Timer t;
	t.reset();
#endif
	for (unsigned int i = m_firstCell; i < m_lastCell; ++i)
	{
		GodunovSolverMT<N>::Cell& c = m_solver.getCellLock(i);
		c.v = c.u / c.r;
		c.p = m_solver.m_gamma1 * (c.e - gdScalar(0.5) * c.u.dotProduct(c.v));
		c.a = sqrt(m_solver.m_gamma * c.p / c.r);
		c.T = c.p / c.r / R;
		m_solver.freeCellLock(i);
	}
#ifdef _PROFILE
	g_updateStatesTimings.addTiming(t.getTimeMiliseconds());
#endif
	fireEvt();
	_DEBUG_PRINT("GodunovWorker::updateStates ended\n");
}

template<unsigned int N>
void GodunovWorker<N>::findMinMax()
{
	_DEBUG_PRINT("GodunovWorker::findMinMax started\n");
	waitForEvt();
#ifdef _PROFILE
	Timer t;
	t.reset();
#endif
	// We access cells read-only, so no need to lock
	for (unsigned int i = m_firstCell; i < m_lastCell; ++i)
	{
		const GodunovSolverMT<N>::Cell& c = m_solver.getCellLock(i);
		m_minValues.a = min(m_minValues.a, c.a);
		m_minValues.e = min(m_minValues.e, c.e);
		m_minValues.p = min(m_minValues.p, c.p);
		m_minValues.r = min(m_minValues.r, c.r);
		m_minValues.T = min(m_minValues.T, c.T);
		m_minValues.v = Min(m_minValues.v, c.v);
		m_minValues.u = Min(m_minValues.u, c.u);

		m_maxValues.a = max(m_maxValues.a, c.a);
		m_maxValues.e = max(m_maxValues.e, c.e);
		m_maxValues.p = max(m_maxValues.p, c.p);
		m_maxValues.r = max(m_maxValues.r, c.r);
		m_maxValues.T = max(m_maxValues.T, c.T);
		m_maxValues.v = Max(m_maxValues.v, c.v);
		m_maxValues.u = Max(m_maxValues.u, c.u);
		m_solver.freeCellLock(i);
	}
#ifdef _PROFILE
	g_findMinMaxTimings.addTiming(t.getTimeMiliseconds());
#endif
	fireEvt();
	_DEBUG_PRINT("GodunovWorker::findMinMax ended\n");
}

template<unsigned int N>
void GodunovWorker<N>::work()
{
	m_minValues.a = FLT_MAX;
	m_minValues.e = FLT_MAX;
	m_minValues.p = FLT_MAX;
	m_minValues.r = FLT_MAX;
	m_minValues.T = FLT_MAX;
	m_minValues.v.set(FLT_MAX);
	m_minValues.u.set(FLT_MAX);

	m_maxValues.a = -FLT_MAX;
	m_maxValues.e = -FLT_MAX;
	m_maxValues.p = -FLT_MAX;
	m_maxValues.r = -FLT_MAX;
	m_maxValues.T = -FLT_MAX;
	m_maxValues.v.set(-FLT_MAX);
	m_maxValues.u.set(-FLT_MAX);

	HANDLE th = CreateThread( 
	NULL,                   // default security attributes
	0,                      // use default stack size  
	&GodunovWorker<N>::ThreadProc,       // thread function name
	this,          // argument to thread function 
	0,                      // use default creation flags 
	NULL);   // returns the thread identifier 
	SetThreadPriority(th, THREAD_PRIORITY_TIME_CRITICAL);
}

template<unsigned int N>
GodunovSolverMT<N>::GodunovSolverMT(gdScalar gamma, gdScalar cfl, unsigned int numThreads, BOOL saveFull)
	: m_gamma(gamma)
	, m_gamma1(gamma - gdScalar(1.0))
	, m_riemann(gamma)
	, m_cfl(cfl)
	, m_minCellSize(0.0)
	, m_pListener(NULL)
	, m_work(false)
	, m_dt(0)
	, m_numThreads(numThreads)
	, m_saveFull(saveFull)
	, m_solvingTime(0)
{
	char buf[256];
	m_events.resize(numThreads);
	m_jobDoneEvents.resize(numThreads);
	for (unsigned int i = 0; i < numThreads; ++i)
	{
		sprintf(buf, "JobDone%d", i);
		m_events[i].jobDone = CreateEvent(NULL, FALSE, FALSE, buf);
		m_jobDoneEvents[i] = m_events[i].jobDone;

		sprintf(buf, "StartJob%d", i);
		m_events[i].startJob = CreateEvent(NULL, FALSE, FALSE, buf);
	}
}

template<unsigned int N>
GodunovSolverMT<N>::~GodunovSolverMT()
{
	for (unsigned int i = 0; i < m_numThreads; ++i)
	{
		CloseHandle(m_events[i].jobDone);

		CloseHandle(m_events[i].startJob);
	}
	m_events.clear();
	m_jobDoneEvents.clear();
}

template<unsigned int N>
void GodunovSolverMT<N>::initModel(unsigned int numCells,
								 unsigned int numFaces,
								 gdScalar minCellSize,
								 gdScalar tolerance, unsigned int stepsThreshold)
{
	m_minCellSize = minCellSize;
	resizeGrid(numCells, numFaces);
	generateGrid();
	generateInitialDistributions();
	applyBoundaryConditions();
	m_riemann.setTolerance(tolerance);
	for (unsigned int i = 0; i < numCells; ++i)
	{
		Cell& c = m_grid[i];
		c.a = sqrt(m_gamma * c.p / c.r);
		c.u = c.v * c.r;
		c.e = c.p / m_gamma1 + gdScalar(0.5) * c.u.dotProduct(c.v);
		c.T = c.p / c.r / R;
	}
}

template<unsigned int N>
void GodunovSolverMT<N>::writeOneStep(FILE* file, const Grid& grid, gdScalar time)
{
	_ASSERT(NULL != file);
	fwrite(&time, sizeof(time), 1, file);

	fwrite(&grid[0], sizeof(GodunovSolverMT<N>::Cell), grid.size(), file);

	fflush(file);
}

template<unsigned int N>
typename GodunovSolverMT<N>::Cell& GodunovSolverMT<N>::getCell(unsigned int id)
{
	_ASSERT(id < m_grid.size());
	return m_grid[id];
}

template<unsigned int N>
typename GodunovSolverMT<N>::Face& GodunovSolverMT<N>::getFace(unsigned int id)
{
	_ASSERT(id < m_faces.size());
	return m_faces[id];
}

template<unsigned int N>
typename GodunovSolverMT<N>::Cell& GodunovSolverMT<N>::getCellLock(unsigned int id)
{
	// TODO: Make it thread-safe
	_ASSERT(id < m_grid.size());
	return m_grid[id];
}

template<unsigned int N>
void GodunovSolverMT<N>::freeCellLock(unsigned int id)
{
	// TODO: Make it thread-safe
}

template<unsigned int N>
typename GodunovSolverMT<N>::Face& GodunovSolverMT<N>::getFaceLock(unsigned int id)
{
	// TODO: Make it thread-safe
	_ASSERT(id < m_faces.size());
	return m_faces[id];
}

template<unsigned int N>
void GodunovSolverMT<N>::freeFaceLock(unsigned int id)
{
	// TODO: Make it thread-safe
}

template<unsigned int N>
typename GodunovSolverMT<N>::SolveResult GodunovSolverMT<N>::solve(gdScalar time, FILE* file)
{
	SolveResult res;

	res.numTimesteps = 1;
	writeOneStep(file, getCurrentState(), 0.0);

	GodunovWorker<N> *workers = (GodunovWorker<N>*)malloc(sizeof(GodunovWorker<N>) * m_numThreads);

	unsigned int facesStep = m_faces.size() / m_numThreads;
	unsigned int cellsStep = m_grid.size() / m_numThreads;
	unsigned int cell = 0;
	unsigned int face = 0;

	for (unsigned int i = 0; i < m_numThreads; ++i)
	{
		unsigned int lastCell = min(cell + cellsStep, m_grid.size());
		unsigned int lastFace = min(face + facesStep, m_faces.size());
		if (i == m_numThreads - 1)
		{
			lastCell = m_grid.size();
			lastFace = m_faces.size();
		}
		new(&workers[i]) GodunovWorker<N>(*this, cell, lastCell, face, lastFace, m_events[i]);
		cell += cellsStep;
		face += facesStep;
		workers[i].work();
	}

	m_solvingTime = GetTickCount();

	m_work = true;

	gdScalar curTime(0.0);
	while(curTime < time)
	{
		SendEvt();
		WaitForEvt();
//		zeroRates();

		SendEvt();
		WaitForEvt();
//		updateRates();

		SendEvt();
		WaitForEvt();
//		normalizeRates();

		SendEvt();
		WaitForEvt();
//		evaluateStepCFL();

		m_dt = FLT_MAX;
		for (unsigned int i = 0; i < m_numThreads; ++i)
			m_dt = min(m_dt, workers[i].m_stepCFL);

		SendEvt();
		WaitForEvt();
//		applyRates();

		applyBoundaryConditions();

		SendEvt();
		WaitForEvt();
//		updateStates();

		SendEvt();
		WaitForEvt();
//		findMinMax();

		curTime += m_dt;
		m_work = (curTime < time);

		SendEvt();
		WaitForEvt();
//		if (!m_solver.checkIfWork())
//			break;

		res.numTimesteps++;

		if (m_saveFull)
			writeOneStep(file, getCurrentState(), curTime);

		if (m_pListener != NULL)
		{
			m_pListener->onStepCompleated(curTime, time, res.numTimesteps);
		}
	}
	res.modelTime = curTime;

	res.minValues.a = FLT_MAX;
	res.minValues.e = FLT_MAX;
	res.minValues.p = FLT_MAX;
	res.minValues.r = FLT_MAX;
	res.minValues.T = FLT_MAX;
	res.minValues.v.set(FLT_MAX);
	res.minValues.u.set(FLT_MAX);

	res.maxValues.a = -FLT_MAX;
	res.maxValues.e = -FLT_MAX;
	res.maxValues.p = -FLT_MAX;
	res.maxValues.r = -FLT_MAX;
	res.maxValues.T = -FLT_MAX;
	res.maxValues.v.set(-FLT_MAX);
	res.maxValues.u.set(-FLT_MAX);
	for (unsigned int i = 0; i < m_numThreads; ++i)
	{
		res.minValues.a = min(res.minValues.a, workers[i].m_minValues.a);
		res.minValues.e = min(res.minValues.e, workers[i].m_minValues.e);
		res.minValues.p = min(res.minValues.p, workers[i].m_minValues.p);
		res.minValues.r = min(res.minValues.r, workers[i].m_minValues.r);
		res.minValues.T = min(res.minValues.T, workers[i].m_minValues.T);
		res.minValues.v = Min(res.minValues.v, workers[i].m_minValues.v);
		res.minValues.u = Min(res.minValues.u, workers[i].m_minValues.u);

		res.maxValues.a = max(res.maxValues.a, workers[i].m_maxValues.a);
		res.maxValues.e = max(res.maxValues.e, workers[i].m_maxValues.e);
		res.maxValues.p = max(res.maxValues.p, workers[i].m_maxValues.p);
		res.maxValues.r = max(res.maxValues.r, workers[i].m_maxValues.r);
		res.maxValues.T = max(res.maxValues.T, workers[i].m_maxValues.T);
		res.maxValues.v = Max(res.maxValues.v, workers[i].m_maxValues.v);
		res.maxValues.u = Max(res.maxValues.u, workers[i].m_maxValues.u);
	}

	m_solvingTime = m_solvingTime - GetTickCount();
	res.solvingTime = m_solvingTime;

	for (unsigned int i = 0; i < m_numThreads; ++i)
	{
		workers[i].~GodunovWorker<N>();
	}
	free(workers);

	if (!m_saveFull)
	{
		writeOneStep(file, getCurrentState(), curTime);
		res.numTimesteps = 2;
	}

	return res;
}

template<unsigned int N>
void GodunovSolverMT<N>::SendEvt()
{
	for (unsigned int i = 0; i < m_events.size(); ++i)
		SetEvent(m_events[i].startJob);
}

template<unsigned int N>
void GodunovSolverMT<N>::WaitForEvt()
{
	WaitForMultipleObjects(m_jobDoneEvents.size(), &m_jobDoneEvents[0], TRUE, INFINITE);
}

template<unsigned int N>
void GodunovSolverMT<N>::resizeGrid(unsigned int numCells,
								  unsigned int numFaces)
{
	m_grid.resize(numCells);
	m_faces.resize(numFaces);
}

#endif // GodunovSolverMT_INCLUDED